package net.pimpas.web.jsf.page.controller;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import net.pimpas.framework.PimpasFramework;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;
import net.pimpas.resourcebundle.exception.MessageKeyNotFoundException;
import net.pimpas.resourcebundle.exception.ResourceBundleNotFoundException;
import net.pimpas.resourcebundle.serviceprovider.ResourceBundleServiceProvider;
import net.pimpas.resourcebundle.serviceprovider.ResourceBundleServiceProviderFactoryImpl;
import net.pimpas.resourcebundle.serviceprovider.ResourceBundleServiceProviderType;

public class PageControllerManager implements Serializable {

	private static final long serialVersionUID = -8085930905949990350L;
	private static final ResourceBundleServiceProvider bundleProvider = 
		ResourceBundleServiceProviderFactoryImpl.getResourceBundleServiceProvider();
	private static final Map<String, List<ResourceBundleEntry>> cache = new HashMap<String, List<ResourceBundleEntry>>();
	private static final LoggingServiceProvider logger = 
		LoggingServiceProviderFactoryImpl.getLoggingServiceProvider(PageControllerManager.class);
	private PageControllerManager( ) {
		
	}
	
	private static final ThreadLocal<PageControllerManager> currentInstance = 
		new ThreadLocal<PageControllerManager>() {
		
			@Override
			protected PageControllerManager initialValue() {
				return null;
			}
		};
	public static PageControllerManager getCurrentInstance( ) {
		if(currentInstance.get() == null)
			currentInstance.set(new PageControllerManager());
		return currentInstance.get();
	}
	
	private boolean hasMessage(PageController controller) {
		PageControl control = controller.getClass().getAnnotation(PageControl.class);
		if(control == null)
			throw new RuntimeException("PageController must have PageControl Annotation!");
		
		// get locale
		Locale l = PimpasFramework.getCurrentInstance().getContext().getLocale();
		if(cache.containsKey(control.pageName())) {
			ResourceBundleEntry entry = null;
			for(ResourceBundleEntry etr: cache.get(control.pageName())) {
				if(etr.getLocale().equals(l)) {
					entry = etr;
					break;
				}
			}
			if(entry != null) {
				controller.setMessages(entry.getMessages());
				logger.debug("[PageControllerManager]: Messages found in cache! Hit!!!");
				return true;
			}
		}
		return false;
	}
	
	public void loadMessages(PageController controller) {
		if(this.hasMessage(controller)) return;
		PageControl control = controller.getClass().getAnnotation(PageControl.class);
		logger.debug("[PageControllerManager]: Loading messages for page ["+control.pageName()+"]");
		// get locale
		Locale l = PimpasFramework.getCurrentInstance().getContext().getLocale();
		synchronized(this) {
			if(this.hasMessage(controller)) return;
			Set<String> set = null;
			for(ResourceBundleServiceProviderType type: ResourceBundleServiceProviderType.values()) {
				try {
					set = bundleProvider.getMessageKeySet(controller.getClass(), type);
					if(set != null)
						for(String key: set)
							controller.getMessages().put(key, bundleProvider.getMessage(controller.getClass(), key, type));
				} catch(ResourceBundleNotFoundException e) {} catch (MessageKeyNotFoundException e) {}
			}
			if(!cache.containsKey(control.pageName()))
				cache.put(control.pageName(), new ArrayList<ResourceBundleEntry>());
			
			List<ResourceBundleEntry> rbe = cache.get(control.pageName());
			rbe.add(new ResourceBundleEntry(l, controller.getMessages()));
		}
	}
	
	private static final class ResourceBundleEntry {
		private Locale locale;
		private Map<String, String> messages;
		
		public ResourceBundleEntry(Locale locale, Map<String, String> messages) {
			this.setLocale(locale);
			this.setMessages(messages);
		}
		
		public Locale getLocale() {
			return locale;
		}
		public void setLocale(Locale locale) {
			this.locale = locale;
		}
		public Map<String, String> getMessages() {
			return messages;
		}
		public void setMessages(Map<String, String> messages) {
			this.messages = messages;
		}
	}
}